﻿/*
Copyright (c) 2012 Vratislav Cermak (vratacermak@gmail.com)
 
This file is part of TeachMe Project. See http://code.google.com/p/teach-me for more information.
 
TeachMe is an open source project. Using of TeachMe is free of charge.
It is distributed under GNU General Public License version 3 (GPLv3).
GPLv3 can be found at http://www.gnu.org/licenses/gpl.html.
 
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Linq;
using System.Windows.Forms;

namespace TeachMe.Application.Pexeso
{
    public class PexesoBoardControl : Panel
    {
        private enum ImageType
        {
            Down,
            DownSelected,
            Up,
            UpSelected,
            Info
        }
        
        private List<PexesDrawContext> _pexeses;
        private int _axisSize;
        private bool _suspendResizeEvents;
        private Dictionary<ImageType, Bitmap> _images = new Dictionary<ImageType, Bitmap>();
        private Font _font;
        private Bitmap _infoImage;
        private int? _interval;
        private Timer _timer;
        private bool _waiting;
        private IBoardCallback _callback;
        private bool _highlightFirstWord;

        public PexesoBoardControl()
        {
            SetStyle(ControlStyles.DoubleBuffer | ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);

            _timer = new Timer { Interval = 100 };
            _timer.Tick += OnTimerClick;
            _timer.Enabled = true;
        }

        public void SetCallback(IBoardCallback callback)
        {
            _callback = callback;
        }

        public bool SuspendResizeEvents
        {
            get { return _suspendResizeEvents; }
            set
            {
                _suspendResizeEvents = value;

                if (!_suspendResizeEvents)
                {
                    LoadImages();
                    Invalidate();
                }
            }
        }

        private IEnumerable<Point> Coordinates
        {
            get
            {
                for (int col = 0; col < AxisSize; col++)
                {
                    for (int row = 0; row < AxisSize; row++)
                    {
                        yield return new Point(col, row);
                    }
                }
            }
        }

        private int AxisSize
        {
            get { return _axisSize; }
        }

        private int PexesesUpCount
        {
            get { return _pexeses.Count(x => x.State == PexesState.Up); }
        }

        private bool Revealed
        {
            get
            {
                if (PexesesUpCount != 2)
                {
                    return false;
                }

                return _callback.Equal(_pexeses.First(x => x.State == PexesState.Up).Parent, _pexeses.Where(x => x.State == PexesState.Up).ElementAt(1).Parent);
            }
        }

        private PexesDrawContext CurrentSelection { get; set; }

        public int FontSize
        {
            set
            {
                _font = new Font(Font.FontFamily, value);

                Invalidate();
            }
        }

        public bool HighlightFirstWord
        {
            set
            {
                _highlightFirstWord = value;

                Invalidate();
            }
        }

        public void Reveal(Pexes pexes)
        {
            _pexeses.First(x => x.Parent == pexes).State = PexesState.Revealed;

            Invalidate(GetRectangle(_pexeses.First(x => x.Parent == pexes).Coordinates));
        }

        public void Load(List<Pexes> pexeses, int axisSize, int fontSize)
        {
            bool loadBitmaps = _axisSize != axisSize;
            _axisSize = axisSize;
            _pexeses = new List<PexesDrawContext>();
            _font = new Font(Font.FontFamily, fontSize);
            _waiting = false;
            
            BoardGenerator generator = new BoardGenerator(axisSize);

            foreach (Pexes pexes in pexeses)
            {
                _pexeses.Add(new PexesDrawContext(pexes, generator.Next()) { State = PexesState.Down });
            }

            if (loadBitmaps)
            {
                LoadImages(); // axis size may have changed
            }

            Invalidate();
        }

        public void ShowInfo(string text, Color color, int interval)
        {
            _infoImage = new Bitmap(_images[ImageType.Info]);

            Font font = new Font(Font.FontFamily, 12, FontStyle.Regular);
            Rectangle rect = new Rectangle(0, 0, _images[ImageType.Info].Width, _images[ImageType.Info].Height);

            using (Graphics graphics = Graphics.FromImage(_infoImage))
            {
                graphics.FillRectangle(new SolidBrush(color), rect);
                graphics.DrawImage(_images[ImageType.Info], rect);

                rect.Inflate(-10, -10);

                graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
                graphics.DrawString(text, font, new SolidBrush(Color.Black), rect, new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
            }

            Invalidate(GetInfoRectangle());

            if (interval > 0)
            {
                _interval = interval * 10;
            }
            else
            {
                _interval = null;
            }
        }

        void OnTimerClick(object sender, EventArgs e)
        {
            if (_interval == null)
            {
                return;
            }

            if (_interval-- <= 0)
            {
                HideInfoImage();
            }
        }

        private Rectangle GetInfoRectangle()
        {
            return new Rectangle(Width / 2 - _infoImage.Width / 2, Height / 2 - _infoImage.Height / 2, _infoImage.Width, _infoImage.Height);   
        }

        private PexesDrawContext GetContextByCoordinates(Point coordinates)
        {
            return _pexeses.First(x => x.Coordinates.X == coordinates.X && x.Coordinates.Y == coordinates.Y);
        }

        private IEnumerable<Point> GetCoordinates(Rectangle r)
        {
            return Coordinates.Where(x => GetRectangle(x).IntersectsWith(r));
        }

        private Rectangle GetRectangle(Point coordinate)
        {
            int cellWidth = Width / AxisSize;
            int cellHeight = Height / AxisSize;

            return new Rectangle(coordinate.X * cellWidth, coordinate.Y * cellHeight, cellWidth, cellHeight);
        }

        private PexesDrawContext GetContextByLocation(Point location)
        {
            foreach (Point coordinates in Coordinates)
            {
                if (GetRectangle(coordinates).Contains(location))
                {
                    return GetContextByCoordinates(coordinates);
                }
            }
            return null;
        }

        private void DrawPexes(Graphics graphics, Point coordinates)
        {
            if (_pexeses == null)
            {
                return;
            }

            Rectangle rect = GetRectangle(coordinates);
            PexesDrawContext context = GetContextByCoordinates(coordinates);

            if (context.State == PexesState.Up)
            {
                rect.Inflate(-5, -5);

                graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
                graphics.DrawString(context.Word, _font, new SolidBrush(Color.Black), rect, new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
            }
        }

        private void DrawBackground(Graphics graphics, Point coordinates)
        {
            if (_pexeses == null)
            {
                return;
            }

            Rectangle rect = GetRectangle(coordinates);
            PexesDrawContext context = GetContextByCoordinates(coordinates);

            if (context == null)
            {
                return;
            }

            bool selected = context == CurrentSelection;

            if (context.State != PexesState.Revealed)
            {
                if (_highlightFirstWord && context.Parent.First)
                {
                    graphics.FillRectangle(new SolidBrush(Color.BlueViolet), rect);
                }

                if (context.State == PexesState.Down)
                {
                    graphics.DrawImage(selected && _infoImage == null ? _images[ImageType.DownSelected] : _images[ImageType.Down], rect);
                }
                else
                {
                    graphics.DrawImage(selected && _infoImage == null ? _images[ImageType.UpSelected] : _images[ImageType.Up], rect);
                }
            }
        }

        private void InvalidateSelection()
        {
            if (CurrentSelection != null)
            {
                Invalidate(GetRectangle(CurrentSelection.Coordinates));
            }
        }

        private void PutDown()
        {
            foreach (PexesDrawContext context in _pexeses.Where(x => x.State == PexesState.Up))
            {
                context.State = PexesState.Down;
            }
        }

        private void LoadImages()
        {
            if (AxisSize == 0)
            {
                return;
            }

            int axisSize = Width / AxisSize;

            ClearImages();

            // original bitmap
            using (Image original = Image.FromStream(Utilities.ReadStreamResource("TeachMe.Application.Embedded.Pexeso.png")))
            {
                // resized 
                using (Bitmap resized = new Bitmap(axisSize * 4, axisSize))
                {
                    using (Graphics graphics = Graphics.FromImage(resized))
                    {
                        graphics.DrawImage(original, 0, 0, axisSize * 4, axisSize);
                    }

                    // extract
                    _images.Add(ImageType.DownSelected, ExtractBitmap(resized, 0));
                    _images.Add(ImageType.Down, ExtractBitmap(resized, axisSize * 1));
                    _images.Add(ImageType.UpSelected, ExtractBitmap(resized, axisSize * 2));
                    _images.Add(ImageType.Up, ExtractBitmap(resized, axisSize * 3));
                }
            }
            
            //
            _images.Add(ImageType.Info, new Bitmap(Image.FromStream(Utilities.ReadStreamResource("TeachMe.Application.Embedded.PexesoInfoCard.png"))));
        }

        private void ClearImages()
        {
            foreach (KeyValuePair<ImageType, Bitmap> pair in _images)
            {
                pair.Value.Dispose();
            }
            _images.Clear();
        }

        private Bitmap ExtractBitmap(Image source, int x)
        {
            int axisSize = Width / AxisSize;
            Bitmap result = new Bitmap(axisSize, axisSize);

            using (Graphics graphics = Graphics.FromImage(result))
            {
                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                graphics.DrawImage(source, new Rectangle(0, 0, axisSize, axisSize), new Rectangle(x, 0, axisSize, axisSize), GraphicsUnit.Pixel);
            }

            return result;
        }

        private void HideInfoImage()
        {
            Rectangle rect = GetInfoRectangle();

            _interval = null; // there may have been interval
            _infoImage = null;

            Invalidate(rect);
            InvalidateSelection();
        }

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            e.Graphics.FillRectangle(new SolidBrush(BackColor), e.ClipRectangle);

            foreach (Point coordinate in GetCoordinates(e.ClipRectangle))
            {
                DrawBackground(e.Graphics, coordinate);
            }
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            foreach (Point coordinate in GetCoordinates(e.ClipRectangle))
            {
                DrawPexes(e.Graphics, coordinate);
            }

            if (_infoImage != null)
            {
                // TODO: improve by drawing only when paint needed
                e.Graphics.DrawImage(_infoImage, GetInfoRectangle());
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            PexesDrawContext context = GetContextByLocation(e.Location);

            if (context == null || CurrentSelection == context)
            {
                return;
            }

            PexesDrawContext lastSelection = CurrentSelection;

            CurrentSelection = context;

            if (lastSelection != null)
            {
                Invalidate(GetRectangle(lastSelection.Coordinates));
            }

            InvalidateSelection();
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            base.OnMouseLeave(e);

            if (CurrentSelection == null)
            {
                return;
            }

            PexesDrawContext lastSelection = CurrentSelection;

            CurrentSelection = null;

            Invalidate(GetRectangle(lastSelection.Coordinates));
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            if (_infoImage != null)
            {
                HideInfoImage();

                if (_pexeses.All(x => x.State == PexesState.Revealed))
                {
                    _callback.EndGame();
                }

                return;
            }

            if (CurrentSelection == null)
            {
                return;
            }

            while (true)
            {
                if (PexesesUpCount == 2 && _waiting)
                {
                    _waiting = false;

                    PexesDrawContext first = _pexeses.First(x => x.State == PexesState.Up);
                    PexesDrawContext second = _pexeses.Where(x => x.State == PexesState.Up).ElementAt(1);

                    List<Point> toInvalidate = new List<Point> {first.Coordinates, second.Coordinates};

                    if (_callback.Equal(first.Parent, second.Parent))
                    {
                        first.State = second.State = PexesState.Revealed;

                        _callback.PexesesRevealed(first.Parent, second.Parent);

                        foreach (Point coordinates in toInvalidate)
                        {
                            Invalidate(GetRectangle(coordinates));
                        }

                        return;
                    }

                    PutDown();
                    
                    _callback.SwitchPlayer();

                    foreach (Point coordinates in toInvalidate)
                    {
                        Invalidate(GetRectangle(coordinates));
                    }

                    return;
                }

                switch (CurrentSelection.State)
                {
                    case PexesState.Down:
                        CurrentSelection.State = PexesState.Up;
                        break;
                    case PexesState.Up:
                        break;
                    case PexesState.Revealed:
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }

                if (PexesesUpCount == 2)
                {
                    _waiting = true;

                    if (Revealed)
                    {   
                        continue;
                    }
                }

                InvalidateSelection();

                break;
            }
        }
    }
}
